#include "mcursorsplugin.h"
#include "mcursorsview.h"
#include "mcursorsconfig.h"

#include <functional>

#include <KTextEditor/Document>
#include <KTextEditor/View>
#include <KTextEditor/MovingInterface>

#include <KPluginFactory>
#include <KPluginLoader>
#include <KLocale>
#include <KAction>
#include <KActionCollection>
#include <KActionMenu>

#include <QMenu>
#include <KTextEditor/MovingCursor>
#include <KConfigGroup>

MCursorsPlugin *MCursorsPlugin::plugin = 0;

K_PLUGIN_FACTORY_DEFINITION(MCursorsPluginFactory,
	registerPlugin<MCursorsPlugin>("ktexteditor_mcursors");
	registerPlugin<MCursorsConfig>("ktexteditor_mcursors_config");
)
K_EXPORT_PLUGIN(MCursorsPluginFactory("ktexteditor_mcursors", "ktexteditor_plugins"))

MCursorsPlugin::MCursorsPlugin(QObject *parent, const QVariantList &args)
: KTextEditor::Plugin(parent)
{
	Q_UNUSED(args);
	plugin = this;

	readConfig();
}

MCursorsPlugin::~MCursorsPlugin()
{
	plugin = 0;
}

void MCursorsPlugin::addView(KTextEditor::View *view)
{
	MCursorsView *nview = new MCursorsView(view);
	nview->setCursorColor(m_cursor_color);
	nview->setUnderlineStyle(m_underline_style);
	nview->setUnderlineColor(m_underline_color);
	m_views.append(nview);
}

void MCursorsPlugin::removeView(KTextEditor::View *view)
{
	for (int z = 0, size = m_views.size(); z < size; z++) {
		if (m_views.at(z)->parentClient() == view) {
			MCursorsView *nview = m_views.at(z);
			size -= m_views.removeAll(nview);
			delete nview;
		}
	}
}

void MCursorsPlugin::readConfig()
{
	KConfigGroup cg(KGlobal::config(), "MCursors Plugin");
	const MCursorsConfig::DefaultValues values;
	m_cursor_color = cg.readEntry("cursor_color", values.cursorColor);
	m_underline_color = cg.readEntry("underline_color", values.underlineColor);
	m_underline_style = MCursorsConfig::getUnderlineStyle(cg.readEntry("underline_style", values.underlineStyle));
}

void MCursorsPlugin::writeConfig()
{
	KConfigGroup cg(KGlobal::config(), "MCursors Plugin");
	cg.writeEntry("cursor_color", m_cursor_color);
	cg.writeEntry("underline_style", MCursorsConfig::getIndexUnderlineStyle(m_underline_style));
	cg.writeEntry("underline_color", m_underline_color);
}


MCursorsView::MCursorsView(KTextEditor::View *view)
: QObject(view)
, KXMLGUIClient(view)
, m_view(view)
, m_document(view->document())
, m_smart(qobject_cast<KTextEditor::MovingInterface*>(m_document))
, m_text_edit(false)
, m_active(true)
, m_synchronize(false)
, m_cursor()
{
	setComponentData(MCursorsPluginFactory::componentData());

#define ENTRY(_Text, _Name, _Receiver) \
	action = new KAction(i18n(_Text), this);\
	actionCollection()->addAction(_Name, action);\
	connect(action, SIGNAL(triggered()), this, SLOT(_Receiver));

	KAction *action;

	/*ENTRY("info cursors", "info_mcursors", debug());
	action->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_I);*/

	ENTRY("Set MCursors", "set_mcursors", setCursor());
	action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C);

	ENTRY("Backspace Character for MCursors", "backspace_mcursors", textBackspace());
	action->setShortcut(Qt::ALT + Qt::Key_Backspace);

	ENTRY("Delete Character for MCursors", "del_mcursors", textDelete());
	action->setShortcut(Qt::ALT + Qt::Key_Delete);

	ENTRY("Remove all MCursors", "remove_all_mcursors", removeAll());
	action->setShortcut(Qt::ALT + Qt::SHIFT + Qt::Key_Delete);

	ENTRY("Remove line MCursors", "remove_line_mcursors", removeLine());
	action->setShortcut(Qt::CTRL + Qt::ALT +  Qt::Key_Delete);

	ENTRY("Move next MCursors", "move_next_mcursors", moveNext());
	action->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_H);

	ENTRY("Move prev MCursors", "move_prev_mcursors", movePrev());
	action->setShortcut(Qt::CTRL + Qt::ALT + Qt::SHIFT + Qt::Key_H);

	ENTRY("Set active MCursors", "active_mcursors", setActive())
	action->setCheckable(true);
	action->setChecked(true);

	ENTRY("Synchronize with the current cursor", "synchronise_mcursors", setSynchronize());
	action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P);
	action->setCheckable(true);

#undef ENTRY

	setEnabled(false);
	setXMLFile("mcursorsui.rc");
}

MCursorsView::~MCursorsView()
{
	removeAll();
}

const KTextEditor::MovingCursor& MCursorsView::_cursor(const Ranges::iterator& it)
{
	return (*it)->start();
}

int MCursorsView::_line(const Ranges::iterator& it)
{
	return _cursor(it).line();
}

int MCursorsView::_column(const Ranges::iterator& it)
{
	return _cursor(it).column();
}

const KTextEditor::MovingCursor& MCursorsView::_cursor(Ranges::const_iterator& it)
{
	return (*it)->start();
}

void MCursorsView::exclusiveEditStart(KTextEditor::Document *doc)
{
	Q_UNUSED(doc);
	m_text_edit = true;
}

void MCursorsView::exclusiveEditEnd(KTextEditor::Document *doc)
{
	Q_UNUSED(doc);
	m_text_edit = false;
}

void MCursorsView::textBackspace()
{
	if (startEditing()) {
		removeTextPrev(1);
		endEditing();
	}
}

void MCursorsView::textDelete()
{
	if (startEditing()) {
		removeTextNext(1);
		endEditing();
	}
}

void MCursorsView::addRange(KTextEditor::MovingRange* range)
{
	if (m_ranges.empty())
	{
		m_ranges.push_front(Ranges::value_type(range));
		actionStartCurses();
	} else {
		Ranges::iterator end = m_ranges.end();
		if (_cursor(--end) < range->start()) {
			m_ranges.push_back(Ranges::value_type(range));
			return ;
		}

		Ranges::iterator it = m_ranges.begin();
		if (range->start() < _cursor(it)) {
			m_ranges.push_front(Ranges::value_type(range));
			return ;
		}

		while (++it != end && range->start() > _cursor(it)) {
		}
		m_ranges.insert(it, Ranges::value_type(range));
	}
}

#define MSIGNAL_OBJECT(O, F, P) F(O, SIGNAL(P), this, SLOT(P))
#define MSIGNAL(F, P) MSIGNAL_OBJECT(m_document, F, P)

#define MSIGNALCURSES(F)\
	do { \
		MSIGNAL(F, textRemoved(KTextEditor::Document*,KTextEditor::Range,QString));\
		MSIGNAL(F, textInserted(KTextEditor::Document*,KTextEditor::Range));\
		MSIGNAL(F, exclusiveEditStart(KTextEditor::Document*));\
		MSIGNAL(F, exclusiveEditEnd(KTextEditor::Document*));\
	} while (0)

#define MSIGNALCURSES_SYNCHRONISE(F)\
	MSIGNAL_OBJECT(m_view, F, cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor))

void MCursorsView::connectCurses()
{
	MSIGNALCURSES(connect);
}

void MCursorsView::disconnectCurses()
{
	MSIGNALCURSES(disconnect);
	if (m_synchronize) {
		m_synchronize = false;
		MSIGNALCURSES_SYNCHRONISE(disconnect);
	}
}

#undef MSIGNALCURSES

void MCursorsView::setSynchronize()
{
	if (m_synchronize) {
		m_synchronize = false;
		MSIGNALCURSES_SYNCHRONISE(disconnect);
	} else {
		m_synchronize = true;
		m_cursor = m_view->cursorPosition();
		MSIGNALCURSES_SYNCHRONISE(connect);
	}
}

#undef MSIGNALCURSES_SYNCHRONISE
#undef MSIGNAL
#undef MSIGNAL_OBJECT

void MCursorsView::actionEmptyCurses()
{
	disconnectCurses();
	setEnabled(false);
}

void MCursorsView::actionStartCurses()
{
	connectCurses();
	setEnabled(true);
}

void MCursorsView::setEnabled(bool x)
{
	actionCollection()->action("backspace_mcursors")->setEnabled(x);
	actionCollection()->action("remove_all_mcursors")->setEnabled(x);
	actionCollection()->action("remove_line_mcursors")->setEnabled(x);
	actionCollection()->action("move_next_mcursors")->setEnabled(x);
	actionCollection()->action("move_prev_mcursors")->setEnabled(x);
	actionCollection()->action("synchronise_mcursors")->setEnabled(x);
}

void MCursorsView::rangePositionChanged(Ranges::iterator& it, const KTextEditor::Cursor& cursor, KTextEditor::Cursor& cursor_prev)
{
	if (cursor_prev == cursor) {
		it = m_ranges.erase(it);
	} else {
		(*it)->setRange(rangeMCursor(cursor));
		cursor_prev = cursor;
		++it;
	}
}

void MCursorsView::rangePositionChanged(Ranges::iterator& it, int line, KTextEditor::Cursor& cursor_prev)
{
	rangePositionChanged(it, KTextEditor::Cursor(line, std::min(_column(it), m_document->lineLength(line))), cursor_prev);
}

void MCursorsView::cursorPositionChanged(KTextEditor::View* view, const KTextEditor::Cursor& cursor)
{
	Q_UNUSED(view);
	int column = 0;
	if (cursor.line() < m_cursor.line()) {
		int diff = m_cursor.line() - cursor.line();
		if (diff == 1 && m_cursor.column() == 0) {
			--column;
		}
		else
		{
			Ranges::iterator it = m_ranges.begin(), end = m_ranges.end();
			while (it != end && _line(it) <= diff - 1) {
				it = m_ranges.erase(it);
			}
			if (it != end) {
				KTextEditor::Cursor cursor_prev;
				do {
					rangePositionChanged(it, _line(it) - diff, cursor_prev);
				} while (it != end);
			}
		}
	} else if (cursor.line() > m_cursor.line()) {
		int diff = cursor.line() - m_cursor.line();
		if (diff == 1 && cursor.column() == 0 && m_document->lineLength(m_cursor.line()) == m_cursor.column()) {
			++column;
		}
		else
		{
			int endLine = m_document->lines();
			Ranges::iterator it = m_ranges.begin(), end = m_ranges.end();
			KTextEditor::Cursor cursor_prev(-1,-1);
			for (; it != end && _line(it) + diff < endLine;) {
				rangePositionChanged(it, _line(it) + diff, cursor_prev);
			}
			if (it != end)
				it = m_ranges.erase(it, end);
		}
	}
	if (!column) {
		column = cursor.column() - m_cursor.column();
	}
	if (column < 0) {
		column = -column;
		KTextEditor::Cursor cursor_prev;
		for (Ranges::iterator it = m_ranges.begin(), end = m_ranges.end(); it != end;) {
			rangePositionChanged(it, recoil(_cursor(it), column), cursor_prev);
		}
	} else if (column > 0) {
		KTextEditor::Cursor cursor_prev(-1,-1);
		for (Ranges::iterator it = m_ranges.begin(), end = m_ranges.end(); it != end;) {
			rangePositionChanged(it, advance(_cursor(it), column), cursor_prev);
		}
	}
	m_cursor = cursor;
}

KTextEditor::Range MCursorsView::rangeMCursor(const KTextEditor::Cursor& cursor)
{
	return KTextEditor::Range(cursor, cursor.line(), cursor.column() + 1);
}

KTextEditor::MovingRange* MCursorsView::newRange(const KTextEditor::Cursor& cursor) const
{
	return m_smart->newMovingRange(rangeMCursor(cursor));
}

void MCursorsView::setCursor(const KTextEditor::Cursor& cursor)
{
	Ranges::iterator it = findRangeIterator<>(std::equal_to<KTextEditor::Cursor>(), cursor);
	if (it != m_ranges.end()) {
		removeRange(it);
	} else {
		KTextEditor::Attribute* attr = new KTextEditor::Attribute;
		attr->setBackground(m_cursor_color);
		if (m_underline_style != QTextCharFormat::NoUnderline) {
			attr->setUnderlineStyle(m_underline_style);
			attr->setUnderlineColor(m_underline_color);
		}
		KTextEditor::MovingRange* range = newRange(cursor);
		range->setAttribute(KTextEditor::Attribute::Ptr(attr));
		addRange(range);
	}
}

void MCursorsView::setCursor()
{
	if (m_view->selection()) {
		const KTextEditor::Range& range = m_view->selectionRange();
		for (int line = range.start().line(); line != range.end().line() + 1; ++line) {
			setCursor(KTextEditor::Cursor(line, std::min(range.start().column(), m_document->lineLength(line))));
		}
	} else {
		setCursor(m_view->cursorPosition());
	}
}

void MCursorsView::textInserted(KTextEditor::Document *doc, const KTextEditor::Range &range)
{
	if (startEditing()) {
		insertText(doc->text(range));
		endEditing();
	}
}

void MCursorsView::insertText(const QString &text)
{
	for (Ranges::const_iterator it = m_ranges.begin(), end = m_ranges.end(); it != end; ++it) {
		m_document->insertText(_cursor(it), text);
	}
}

KTextEditor::Cursor MCursorsView::advance(const KTextEditor::Cursor& cursor, int length) const
{
	KTextEditor::Cursor ret(cursor);
	int lines = m_document->lines();
	int line_length = m_document->lineLength(ret.line());
	if (ret.column() + length - 1 < line_length)
		ret.setColumn(ret.column() + length);
	else
	{
		length -= line_length - ret.column() + 1;
		ret.setColumn(0);
		ret.setLine(ret.line() + 1);
		while (length && ret.line() < lines)
		{
			line_length = m_document->lineLength(ret.line());
			if (line_length < length)
			{
				ret.setLine(ret.line() + 1);
				length -= line_length - 1;
			}
			else
			{
				ret.setColumn(length);
				length = 0;
			}
		}
	}
	return ret;
}

KTextEditor::Cursor MCursorsView::recoil(const KTextEditor::Cursor& cursor, int length) const
{
	if (cursor.column() >= length)
		return KTextEditor::Cursor(cursor.line(), cursor.column() - length);
	KTextEditor::Cursor ret(cursor.line() - 1, 0);
	length -= cursor.column();
	while (length && ret.line() >= 0)
	{
		int line_length = m_document->lineLength(ret.line());
		if (line_length < length)
		{
			ret.setLine(ret.line() - 1);
			length -= line_length - 1;
		}
		else
		{
			ret.setColumn(line_length - length + 1);
			length = 0;
		}
	}
	return ret;
}

/*void MCursorsView::debug() const
{
	for (Ranges::const_iterator it = m_ranges.begin(), end = m_ranges.end(); it != end; ++it) {
		qDebug() << **it;
	}
}*/

void MCursorsView::textRemoved(KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& text)
{
	Q_UNUSED(doc);
	Q_UNUSED(range);
	if (startEditing()) {
		removeTextPrev(text.length());
		endEditing();
	}
}

void MCursorsView::removeRange(const Ranges::iterator &it)
{
	m_ranges.erase(it);
	if (m_ranges.empty())
		actionEmptyCurses();
}

void MCursorsView::removeAll()
{
	if (m_view->selection()) {
		const KTextEditor::Range& range = m_view->selectionRange();
		Ranges::iterator first = findRangeIterator<>(std::greater_equal<KTextEditor::Cursor>(), range.start());
		Ranges::iterator end = m_ranges.end();
		if (first != end) {
			Ranges::iterator last = findRangeIterator<>(first, end, std::greater_equal<KTextEditor::Cursor>(), range.end());
			if (last != end) {
				m_ranges.erase(first, last);
				if (m_ranges.empty())
					actionEmptyCurses();
			}
		}
	} else {
		m_ranges.clear();
		actionEmptyCurses();
	}
}

void MCursorsView::removeLine()
{
	int line = m_view->cursorPosition().line();
	for (Ranges::iterator it = m_ranges.begin(); it != m_ranges.end();) {
		if (_line(it) == line) {
			it = m_ranges.erase(it);
		} else {
			++it;
		}
	}
	if (m_ranges.empty())
		actionEmptyCurses();
}

void MCursorsView::cleanCursor()
{
	for (Ranges::iterator it = m_ranges.begin(), end = m_ranges.end(); it  != end;) {
		if ((*it)->start() == (*it)->end())
			it = m_ranges.erase(it);
		else
			++it;
	}
}

void MCursorsView::removeTextNext(int length)
{
	KTextEditor::Cursor documentEnd = m_document->documentEnd();
	KTextEditor::Cursor cursor_prev(0,0);
	for (Ranges::iterator it = m_ranges.begin(), end = m_ranges.end(); it != end;) {
		documentEnd = m_document->documentEnd();
		const KTextEditor::MovingCursor& cursor = _cursor(it);
		if (cursor < cursor_prev) {
			it = m_ranges.erase(it);
		} else {
			if (cursor != documentEnd) {
				KTextEditor::Cursor cursor_advance = advance(cursor, length);
				m_document->removeText(KTextEditor::Range(cursor, cursor_advance));
				cursor_prev = cursor_advance;
				(*it)->setRange(rangeMCursor(cursor));
			}
			++it;
		}
	}
	if (length != 1 && m_ranges.size() > 1) {
		cleanCursor();
	}
}

void MCursorsView::removeTextPrev(int length)
{
	KTextEditor::Cursor cursor_prev(0,0);
	for (Ranges::iterator it = m_ranges.begin(), end = m_ranges.end(); it != end;) {
		const KTextEditor::MovingCursor& cursor = _cursor(it);
		if (cursor == KTextEditor::Cursor(0,0)) {
			it = m_ranges.erase(it);
		} else {
			KTextEditor::Cursor cursor = recoil(cursor, length);
			if (cursor > cursor_prev) {
				cursor_prev = cursor;
			}
			m_document->removeText(KTextEditor::Range(cursor_prev, cursor));
			++it;
		}
	}
	if (length != 1 && m_ranges.size() > 1) {
		cleanCursor();
	}
}

template<typename _Functor>
MCursorsView::Ranges::iterator MCursorsView::findRangeIterator(const _Functor& func)
{
	return findRangeIterator<>(func, m_view->cursorPosition());
}

template<typename _Functor>
MCursorsView::Ranges::iterator MCursorsView::findRangeIterator(const _Functor& func, const KTextEditor::Cursor& cursor)
{
	return findRangeIterator<>(m_ranges.begin(), m_ranges.end(), func, cursor);
}

template<typename _Functor>
MCursorsView::Ranges::reverse_iterator MCursorsView::findRangeReverseIterator(const _Functor& func)
{
	return findRangeReverseIterator<>(func, m_view->cursorPosition());
}

template<typename _Functor>
MCursorsView::Ranges::reverse_iterator MCursorsView::findRangeReverseIterator(const _Functor& func, const KTextEditor::Cursor& cursor)
{
	return findRangeIterator<>(m_ranges.rbegin(), m_ranges.rend(), func, cursor);
}

template<typename _Functor, typename _Iterator>
_Iterator MCursorsView::findRangeIterator(_Iterator it, const _Iterator& end, const _Functor& func, const KTextEditor::Cursor& cursor)
{
	for (; it != end && !func((*it)->start(), cursor); ++it) {
	}
	return it;
}

void MCursorsView::moveNext()
{
	Ranges::iterator it = findRangeIterator<>(std::greater<KTextEditor::Cursor>());
	m_view->setCursorPosition(_cursor(it != m_ranges.end() ? it : m_ranges.begin()));
}

void MCursorsView::movePrev()
{
	Ranges::reverse_iterator it = findRangeReverseIterator<>(std::less<KTextEditor::Cursor>());
	m_view->setCursorPosition((it != m_ranges.rend() ? *it : *m_ranges.rbegin())->start());
}

void MCursorsView::setActive()
{
	if (m_active) {
		m_active = false;
		disconnectCurses();
	} else {
		m_active = true;
		connectCurses();
	}
}

bool MCursorsView::startEditing()
{
	if (!m_active || m_text_edit || m_ranges.empty() || !m_document->startEditing())
		return false;
	if (m_synchronize)
		actionCollection()->action("synchronise_mcursors")->trigger();
	return m_text_edit = true;
}

bool MCursorsView::endEditing()
{
	m_text_edit = false;
	return m_document->endEditing();
}

#define SETTER_PLUGIN(_Name, _Type, _Var)\
	void MCursorsPlugin::_Name(_Type _Var) {\
		this->_Var = _Var;\
		for (auto it = m_views.begin(), end = m_views.end();  it != end; ++it) {\
			(*it)->_Name(_Var);\
		}\
	}

SETTER_PLUGIN(setCursorColor, const QColor&, m_cursor_color)
SETTER_PLUGIN(setUnderlineStyle, QTextCharFormat::UnderlineStyle, m_underline_style)
SETTER_PLUGIN(setUnderlineColor, const QColor&, m_underline_color);

#undef SETTER_PLUGIN

#define SETTER_VIEW(_Name, _Type, _Var, _Set)\
	void MCursorsView::_Name(_Type _Var) {\
		this->_Var = _Var;\
		for (Ranges::iterator it = m_ranges.begin(), end = m_ranges.end(); it != end; ++it) {\
			(*it)->attribute()->_Set(_Var);\
		}\
	}

SETTER_VIEW(setCursorColor, const QColor&, m_cursor_color, setBackground)
SETTER_VIEW(setUnderlineStyle, QTextCharFormat::UnderlineStyle, m_underline_style, setUnderlineStyle)
SETTER_VIEW(setUnderlineColor, const QColor&, m_underline_color, setUnderlineColor)

#undef SETTER_VIEW

#include "mcursorsview.moc"
